// Copyright (c) 2012 SameGoal LLC.
// All Rights Reserved.
// Author: Andy Hochhaus <ahochhaus@samegoal.com>

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <gtest/gtest.h>
#include <gflags/gflags.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>

#include <thread>
#include <chrono>

#include "base/init.h"
#include "base/string.h"
#include "net/http/server/event/event_server.h"
#include "net/http/server/event/request.h"
#include "net/http/server/event/handler.h"

DEFINE_string(http_service_address, "127.0.0.1", "HTTP server IP address");
DEFINE_string(http_service_hostname, "localhost", "hostname for HTTP server");
DEFINE_string(http_service_port, "1025", "HTTP server port");
DEFINE_bool(run_loop, false, "Run server in a loop (no unit test) for real "
            "world testing in a browser.");

namespace http_event = ::sg::net::http::server::event;

void HelloWorld(http_event::Request *req, void */* arg */) {
  req->AddResponseHeader("Content-Type", "text/html; charset=utf-8");
  req->Send(http_event::OK, "hello world");
}

void RunServer() {
  http_event::EventServer server(
      FLAGS_http_service_address, FLAGS_http_service_hostname,
      FLAGS_http_service_port, 128);
  http_event::Handler h1(&HelloWorld, NULL);
  h1.set_uri_regex("/hello_world", NULL);
  server.AddHandler(&h1);
  server.Serve();
}

int GetSocket() {
  struct addrinfo hints, *res;
  memset(&hints, 0, sizeof(hints));
  hints.ai_family = AF_UNSPEC;
  hints.ai_socktype = SOCK_STREAM;
  CHECK_EQ(0, getaddrinfo(FLAGS_http_service_address.c_str(),
                          FLAGS_http_service_port.c_str(),
                          &hints,
                          &res));
  int sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
  CHECK_NE(-1, sock);
  CHECK_NE(-1, connect(sock, res->ai_addr, res->ai_addrlen));
  freeaddrinfo(res);
  return sock;
}

void SendRequest(const std::string &req, int sock) {
  CHECK(send(sock, req.c_str(), req.length(), 0) ==
        static_cast<ssize_t> (req.length()));
}

std::string ReadResponse(int sock) {
  bool has_content_length = false, headers_complete = false;
  uint64 content_length, body_offset;
  std::string connection, response, response_no_continue;
  while (true) {
    char buf[1];
    const int bytes_read = recv(sock, buf, sizeof(buf), 0);
    if (bytes_read < 0) {
      LOG(FATAL) << "recv error";
    } else if (bytes_read == 0) {
      // connection was closed
      break;
    } else {
      response.append(buf, bytes_read);
    }

    if (!headers_complete) {
      response_no_continue = response;
      sg::string::ReplaceAll(
          "HTTP/1.1 100 Continue\r\n\r\n", "", &response_no_continue);
      if (response_no_continue.find("\r\n\r\n") != std::string::npos) {
        headers_complete = true;
        body_offset = response_no_continue.find("\r\n\r\n") + 4 +
            (response.length() - response_no_continue.length());
        std::vector<std::string> headers;
        sg::string::Split(response_no_continue, "\r\n", 0, &headers);

        for (auto i = headers.begin(); i != headers.end(); ++i) {
          if (i == headers.begin()) {
            continue;
          } else if (i->empty()) {
            break;
          }
          std::vector<std::string> parts;
          sg::string::Split(*i, ":", 1, &parts);
          CHECK_EQ(2, parts.size());
          std::string key_lower = parts[0];
          sg::string::ToLower(&key_lower);
          std::string value = parts[1];
          sg::string::Strip(&value);
          if (key_lower == "content-length") {
            has_content_length = true;
            CHECK(sg::string::FromString(value, &content_length));
          } else if (key_lower == "connection") {
            connection = value;
          }
        }
      }
    }

    if (headers_complete) {
      if (connection == "chunked" &&
          sg::string::EndsWith(response_no_continue, "0\r\n\r\n")) {
        break;
      } else if (has_content_length &&
                 body_offset + content_length == response.length()) {
        break;
      }
    }
  }
  return response;
}

const std::string http_10_hello_world_req =
    "GET /hello_world HTTP/1.0\r\n"
    "\r\n";
const std::string http_10_hello_world_resp =
    "HTTP/1.0 200 OK\r\n"
    "Content-Type: text/html; charset=utf-8\r\n"
    "Content-Length: 11\r\n"
    "Connection: Close\r\n"
    "\r\n"
    "hello world";

const std::string http_10_persistent_hello_world_req =
    "GET /hello_world HTTP/1.0\r\n"
    "Connection: Keep-Alive\r\n"
    "\r\n";
const std::string http_10_persistent_hello_world_resp =
    "HTTP/1.0 200 OK\r\n"
    "Content-Type: text/html; charset=utf-8\r\n"
    "Content-Length: 11\r\n"
    "Connection: Keep-Alive\r\n"
    "\r\n"
    "hello world";

const std::string http_11_hello_world_req =
    "GET /hello_world HTTP/1.1\r\n"
    "Expect: 100-continue\r\n"
    "\r\n";
const std::string http_11_hello_world_resp =
    "HTTP/1.1 200 OK\r\n"
    "Content-Type: text/html; charset=utf-8\r\n"
    "Content-Length: 11\r\n"
    "Connection: Keep-Alive\r\n"
    "\r\n"
    "hello world";

const std::string http_11_expect_req =
    "GET /hello_world HTTP/1.1\r\n"
    "Expect: 100-continue\r\n"
    "\r\n";
const std::string http_11_expect_resp =
    "HTTP/1.1 200 OK\r\n"
    "Content-Type: text/html; charset=utf-8\r\n"
    "Content-Length: 11\r\n"
    "Connection: Keep-Alive\r\n"
    "\r\n"
    "hello world";

TEST(HttpOne, NotPersistent) {
  int sock = GetSocket();
  SendRequest(http_10_hello_world_req, sock);
  ASSERT_EQ(http_10_hello_world_resp, ReadResponse(sock));
  close(sock);
}

TEST(HttpOne, Persistent) {
  int sock = GetSocket();
  SendRequest(http_10_persistent_hello_world_req, sock);
  ASSERT_EQ(http_10_persistent_hello_world_resp, ReadResponse(sock));
  SendRequest(http_10_persistent_hello_world_req, sock);
  ASSERT_EQ(http_10_persistent_hello_world_resp, ReadResponse(sock));
  close(sock);
}

TEST(HttpOneOne, Expect) {
  int sock = GetSocket();
  SendRequest(http_11_expect_req, sock);
  ASSERT_EQ(http_11_expect_resp, ReadResponse(sock));
  close(sock);
}

TEST(HttpOneOne, Pipelined) {
  int sock = GetSocket();
  SendRequest(http_11_hello_world_req, sock);
  SendRequest(http_11_hello_world_req, sock);
  ASSERT_EQ(http_11_hello_world_resp, ReadResponse(sock));
  ASSERT_EQ(http_11_hello_world_resp, ReadResponse(sock));
  close(sock);
}

const char *usage = "event_server_test\n";

int main(int argc, char **argv) {
  SG_INIT(usage, &argc, &argv, true);
  if (!FLAGS_run_loop) {
    std::thread t(&RunServer);
    t.detach();
    // TODO(ahochhaus): remove sleep race condition
    std::this_thread::sleep_for(std::chrono::milliseconds(500));
    return RUN_ALL_TESTS();
  } else {
    RunServer();
    return 0;
  }
}
